<?php
/*
* Copyright (c) 2010 by Cediti
*
* You may distribute under the terms of either the GNU Lesser General
* Public License or the new BSD License, as specified in the README file.
*/

// created :	18 March 2010
// version :	18 March 2010

// NOTE : the separator should only be on one line no separator on multiple lines!!!!

/**
 * Basic templates class
 *
 * Syntax : something#parent.tagName#somethingother
 * with # as first and last separator and . as parent separator
 *
 */
class Templates
{
	/**
	 * All readyh to print value. It's the block where variable have been replaced
	 * @var Array
	 */
	protected $_rendered;

	/**
	 * Block of string that wait for rendering
	 * @var array
	 */
	protected $_block;

	/**
	 * Tag name by block, associated with a value
	 * @var array
	 */
	protected $_tag;

	/**
	 * Separator of tag inside the block string
	 * @var array
	 */
	protected $_separators;

	/**
	 * Current abse dir to load templates
	 * @var string
	 */
	protected $_baseDir;

	const BLOCK_NO_NAME = ' ';
	const BLOCK_ALL = '*';

	const PLACE_PREFIX = 0;
	const PLACE_POSTFIX = 1;
	const PLACE_REPLACE = 2;

	/**
	 *
	 * @param string $baseDir
	 * @param array[string] $separators tree string array : 0: first separator, 1: last separator, 2: parent separator
	 * @return Templates
	 */
	public function __construct($baseDir = '', $separators = array('#','#','.'))
	{
		$this->setBaseDir($baseDir);
		$this->resetAll();
		if (empty($separators) || !is_array($separators) || !is_string($separators[ 0 ]) || !is_string($separators[ 1 ]) || !is_string($separators[ 2 ]))
		{
			throw new InvalidArgumentException('An array should be submitted with tree strings value');
		}
		$this->_separators = $separators;
	}

	/**
	 * Reset the current value of templates
	 * @return Templates
	 */
	public function resetAll()
	{
		$this->_rendered = array();
		$this->_block = array(
		    self::BLOCK_NO_NAME => array(
			    'loaded'  => false,
			    'content' => array(),
			    'src'     => '',
			    'cpt'     => 0
		    )
		);
		$this->_tag = array(
			self::BLOCK_NO_NAME => array(),
			self::BLOCK_ALL     => array()
		);

		return $this;
	}

	/**
	 * Defined the current base dir (changing it, don't change base dir of previous files
	 * @param string $baseDir, if empty, no change are made
	 * @return Templates
	 */
	public function setBaseDir($baseDir)
	{
		if (!empty($baseDir))
		{
			$this->_baseDir = $baseDir;
		}

		return $this;
	}

	/**
	 * Add a file associated with a block.
	 * If a block exist it will be replace by this one and all render of this block will be lost
	 * @param string $path path of file from basedir
	 * @param const|string $block block name
	 * @param string $baseDir use this base dir instead of current one
	 * @return Templates
	 */
	public function addFile($path, $block = ' ', $isLoop = false, $baseDir = '')
	{
		$this->_block[ $block ] = array(
			'loaded'  => false,
		    'loop'    => $isLoop,
	        'content' => '',
			'cpt'     => 0,
			'src'     => realpath(((!empty($baseDir))?trim($baseDir):$this->_baseDir) . $path)
		);

		$this->_rendered[ $block ] = '';

		//load the files
		$this->_block[ $block ]['content'] = file_get_contents($this->_block[ $block ]['src'], FILE_TEXT);
		if ($this->_block[ $block ]['content'] === false)
		{
			throw new DomainException('File ( \'' . $this->_block[ $block ]['src'] . '\' OR \'' . ((!empty($baseDir))?trim($baseDir):$this->_baseDir) . $path . '\') doesn\'t exist or can\'t be loaded!');
		}
		elseif (empty($this->_block[ $block ]['content']))
		{
			throw new LengthException('File ( ' . $this->_block[ $block ]['src'] . ') is empty!');
		}

		$this->_tag[ $block ] = array();

		$this->_block[ $block ]['loaded'] = true;

		return $this;
	}

	/**
	 * Set a value on a block associated with a tag name
	 * @param string $tag
	 * @param string $value
	 * @param const|string $block
	 * @return Templates
	 */
	public function setVar($tag, $value, $block = ' ')
	{
		return $this->addVar($tag, $value, self::PLACE_REPLACE, $block);
	}

	/**
	 * Set a value on a block associated with a tag name and a parent tag
	 * @param string $tag
	 * @param string $parentTag
	 * @param string $value
	 * @param const|string $block
	 * @return Templates
	 */
	public function setSubVar($tag, $parentTag, $value, $block = ' ')
	{
		return $this->addVar($parentTag . $this->_separators[ 2 ] . $tag, $value, self::PLACE_REPLACE, $block);
	}

    /**
     * Set a set of value on a block associated with a tag name and a parent tag
     * @param array $values aray of tag=>value
     * @param string $parentTag
     * @param const|string $block
     * @return Templates
     */
    public function setSubVars($values, $parentTag, $block = ' ')
    {
        $this->_blockExist($block);

        foreach($values as $tag => $value)
        {
            $this->setSubVar($tag, $parentTag, $value, $block);
        }

        return $this;
    }

    public function setSubLoopVars($values, $parentTag, $block = ' ')
    {
        $this->_blockExist($block);

        $pre = $this->_separators[ 0 ] . $parentTag . $this->_separators[ 2 ];

        foreach($values as $tag => $value)
        {
            $this->_tag[ $block ][ $this->_block[ $block ]['cpt'] ][ $pre . $tag . $this->_separators[ 1 ] ] = $value;
        }

        $this->_block[ $block ]['cpt']++;

        return $this;
    }

    public function setLoopVars($values, $block = ' ')
    {
    	$this->_blockExist($block);

        foreach($values as $tag => $value)
        {
        	$this->_tag[ $block ][ $this->_block[ $block ]['cpt'] ][ $this->_separators[ 0 ] . $tag . $this->_separators[ 1 ] ] = $value;
        }

        $this->_block[ $block ]['cpt']++;

        return $this;
    }

	/**
	 * Concactain a value on a block associated with a tag name
	 * @param string $tag
	 * @param string $value
	 * @param const $placewhere to place the value
	 * @param const|string $block
	 * @return Templates
	 */
	public function addVar($tag, $value, $place, $block = ' ')
	{
		$this->_blockExist($block);

		//check for bad value location in loop
		if ($this->_block[ $block ]['loop'])
		{
			throw new Exception($block . ' is a loop block');
		}

		$tag = $this->_separators[ 0 ] . $tag . $this->_separators[ 1 ];

		switch ($place)
		{
			default :
			case self::PLACE_REPLACE :
				$this->_tag[ $block ][ $tag ] = $value;
				break;
			case self::PLACE_POSTFIX :
				$this->_tag[ $block ][ $tag ] .= $value;
				break;
			case self::PLACE_PREFIX :
				$this->_tag[ $block ][ $tag ] = $value . $this->_tag[ $block ][ $tag ];
				break;
		}

		return $this;
	}

	/**
	 * Rendered a block and return it
	 * If already red, return the previous one
	 * @param const|string $block
	 * @param bool $force force to reread it
	 * @param bool $reload if forced and true the block will be reloaded
	 * @return string
	 */
	public function render($block = ' ', $force = false, $reload = true)
	{
		$this->_blockExist($block);

		if ($block == self::BLOCK_ALL)
		{
			return '';
		}
		elseif (!$this->_block[ $block ]['loaded'])
		{
			throw new ErrorException('The file was not loaded and so, can \'t be rendered...');
		}
		elseif ($force && $reload)
		{
			$this->addFile($this->_block[ $block ]['src'], $block, ' ');
		}
		elseif ($force && $this->_block[ $block ]['loop'])
		{
			throw new ErrorException('Can\'t force a loop block!');
		}
		elseif (!$force && isset($this->_rendered[ $block ]) && !empty($this->_rendered[ $block ]))
		{
			//return already rendered $block
			return $this->_rendered[ $block ];
		}
		elseif (!$force)
		{
			if (!$this->_block[ $block ]['loop'])
			{
				$this->_rendered[ $block ] = $this->_block[ $block ]['content'];
			}
			else
			{
                $this->_rendered[ $block ] = '';
			}
		}

		//replace block by value
		if ($this->_block[ $block ]['loop'])
		{
			//if loop we render it as an array
            foreach($this->_tag[ $block ] as $tags)
            {
            	$tmp = $this->_block[ $block ]['content'];
            	foreach($tags as $tag => $value)
            	{
            		$tmp = str_replace($tag, $value, $tmp);
            	}
            	$this->_rendered[ $block ] .= $tmp;
            }
		}
		else
		{
			foreach($this->_tag[ $block ] as $tag => $value)
			{
				$this->_rendered[ $block ] = str_replace($tag, $value, $this->_rendered[ $block ]);
			}
		}

		//replace the all block value ...
		foreach($this->_tag[ self::BLOCK_ALL ] as $tag => $value)
		{
			$this->_rendered[ $block ] = str_replace($tag, $value, $this->_rendered[ $block ]);
		}

		return $this->_rendered[ $block ];
	}

	protected function _blockExist($block)
	{
		if (!isset($this->_tag[ $block ]) || !isset($this->_block[ $block ]))
		{
//			Debug::var_debug(array_keys($this->_block), $this->_block);
//			Debug::var_debug(array_keys($this->_tag), $this->_tag);
			throw new InvalidArgumentException('Block ( ' . $block . ' ) doesn\'t exist!');
		}
	}
}

# EOF
